package de.dailab.graphworld.ontology;

import java.util.ArrayList;
import java.util.Random;

import de.dailab.jiactng.agentcore.knowledge.IFact;


public class Graph implements IFact{
	
	private static final long serialVersionUID = -7168844869256107613L;
	private ArrayList<Node> nodes;
	private int nodeID = 0;
	
	/**
	 * constructor to create a random graph looking like a grid
	 * and to place random units of gold on the nodes
	 * @param rnd Random variable to place gold
	 * @param nodecount can be 2, 4, 9, 25 or 100 (25 by default)
	 * @param maximum amount of gold per node
	 * @param decay decay factor for pheromones
	 */
	public Graph(Random rnd, int nodecount, int goldPerNode, double decay){
		// creates 100-node graph
		if(nodecount==100){
			nodes = new ArrayList<Node>();
			nodes.add(new Node(nodeID, 0, 0, 0));
			nodeID++;
			while (nodeID < 100){
				nodes.add(new Node(nodeID, (nodeID%10)*50, (nodeID/10)*50, 
						(rnd.nextBoolean()&&rnd.nextBoolean()&&rnd.nextBoolean() ? rnd.nextInt(goldPerNode) + 1 : 0)));
				nodeID++;
			}
			for (Node n : nodes){
				if((n.getID()%10) < 9){
					n.addEdge(1, nodes.get(n.getID()+1), decay);
					nodes.get(n.getID()+1).addEdge(1, n, decay);
				}
				if(n.getID() < 90){
					n.addEdge(1, nodes.get(n.getID()+10), decay);
					nodes.get(n.getID()+10).addEdge(1, n, decay);
				}
				if((n.getID()%10) > 0 && n.getID() < 90){
					n.addEdge(1, nodes.get(n.getID()+9), decay);
					nodes.get(n.getID()+9).addEdge(1, n, decay);
				}
				if((n.getID()%10) < 9 && n.getID() < 90){
					n.addEdge(1, nodes.get(n.getID()+11), decay);
					nodes.get(n.getID()+11).addEdge(1, n, decay);
				}
			}
		} else if(nodecount==2){
			nodes = new ArrayList<Node>();
			nodes.add(new Node(nodeID, 50, 50, 0));
			nodeID++;
			nodes.add(new Node(nodeID, 400, 400, 100));
			nodeID++;
			nodes.get(0).addEdge(1, nodes.get(1), decay);
			nodes.get(1).addEdge(1, nodes.get(0), decay);
		} else if(nodecount==4){
			// creates 4-node graph
			nodes = new ArrayList<Node>();
			nodes.add(new Node(nodeID, 0, 0, 0));
			nodeID++;
			nodes.add(new Node(nodeID, 0, 400, 0));
			nodeID++;
			nodes.add(new Node(nodeID, 400, 0, 0));
			nodeID++;
			nodes.add(new Node(nodeID, 400, 400, 100));
			nodeID++;
			nodes.get(0).addEdge(1, nodes.get(1), decay);
			nodes.get(0).addEdge(1, nodes.get(2), decay);
			nodes.get(0).addEdge(1, nodes.get(3), decay);
			nodes.get(1).addEdge(1, nodes.get(0), decay);
			nodes.get(1).addEdge(1, nodes.get(2), decay);
			nodes.get(1).addEdge(1, nodes.get(3), decay);
			nodes.get(2).addEdge(1, nodes.get(0), decay);
			nodes.get(2).addEdge(1, nodes.get(1), decay);
			nodes.get(2).addEdge(1, nodes.get(3), decay);
			nodes.get(3).addEdge(1, nodes.get(0), decay);
			nodes.get(3).addEdge(1, nodes.get(1), decay);
			nodes.get(3).addEdge(1, nodes.get(2), decay);
		} else if(nodecount==9){
			// creates 9-node graph
			nodes = new ArrayList<Node>();
			nodes.add(new Node(nodeID, 0, 0, 0));
			nodeID++;
			nodes.add(new Node(nodeID, 0, 200, 0));
			nodeID++;
			nodes.add(new Node(nodeID, 0, 400, 0));
			nodeID++;
			nodes.add(new Node(nodeID, 200, 0, 0));
			nodeID++;
			nodes.add(new Node(nodeID, 200, 200, 0));
			nodeID++;
			nodes.add(new Node(nodeID, 200, 400, 0));
			nodeID++;
			nodes.add(new Node(nodeID, 400, 0, 0));
			nodeID++;
			nodes.add(new Node(nodeID, 400, 200, 0));
			nodeID++;
			nodes.add(new Node(nodeID, 400, 400, 100));
			nodeID++;
			for (Node n : nodes){
				if((n.getID()%3) < 2){
					n.addEdge(1, nodes.get(n.getID()+1), decay);
					nodes.get(n.getID()+1).addEdge(1, n, decay);
				}
				if(n.getID() < 6){
					n.addEdge(1, nodes.get(n.getID()+3));
					nodes.get(n.getID()+3).addEdge(1, n);
				}
				if((n.getID()%3) < 2 && n.getID() < 6){
					n.addEdge(1, nodes.get(n.getID()+4), decay);
					nodes.get(n.getID()+4).addEdge(1, n, decay);
				}
				if((n.getID()%3) > 0 && n.getID() < 6){
					n.addEdge(1, nodes.get(n.getID()+2), decay);
					nodes.get(n.getID()+2).addEdge(1, n, decay);
				}
			}
		} else if (nodecount==16) {
			// creates 16-node-graph	
				nodes = new ArrayList<Node>();
				nodes.add(new Node(nodeID, 20, 20, 0));
				nodeID++;
				while (nodeID < 16){
					nodes.add(new Node(nodeID, (nodeID%4)*100 + 20, (nodeID/4)*100 + 20, 
							(rnd.nextBoolean()&&rnd.nextBoolean() ? rnd.nextInt(goldPerNode) + 1 : 0)));
					nodeID++;
				}
				for (Node n : nodes){
					if((n.getID()%4) < 3){
						n.addEdge(1, nodes.get(n.getID()+1), decay);
						nodes.get(n.getID()+1).addEdge(1, n, decay);
					}
					if(n.getID() < 12){
						n.addEdge(1, nodes.get(n.getID()+4), decay);
						nodes.get(n.getID()+4).addEdge(1, n, decay);
					}
					if((n.getID()%4) > 0 && n.getID() < 12){
						n.addEdge(1, nodes.get(n.getID()+3), decay);
						nodes.get(n.getID()+3).addEdge(1, n, decay);
					}
					if((n.getID()%4) < 3 && n.getID() < 12){
						n.addEdge(1, nodes.get(n.getID()+5), decay);
						nodes.get(n.getID()+5).addEdge(1, n, decay);
					}
				}
		} else {
			// creates 25-node-graph	
			nodes = new ArrayList<Node>();
			nodes.add(new Node(nodeID, 20, 20, 0));
			nodeID++;
			while (nodeID < 25){
				nodes.add(new Node(nodeID, (nodeID%5)*100 + 20, (nodeID/5)*100 + 20, 
						(rnd.nextBoolean()&&rnd.nextBoolean() ? rnd.nextInt(goldPerNode) + 1 : 0)));
				nodeID++;
			}
			for (Node n : nodes){
				if((n.getID()%5) < 4){
					n.addEdge(1, nodes.get(n.getID()+1), decay);
					nodes.get(n.getID()+1).addEdge(1, n, decay);
				}
				if(n.getID() < 20){
					n.addEdge(1, nodes.get(n.getID()+5), decay);
					nodes.get(n.getID()+5).addEdge(1, n, decay);
				}
				if((n.getID()%5) > 0 && n.getID() < 20){
					n.addEdge(1, nodes.get(n.getID()+4), decay);
					nodes.get(n.getID()+4).addEdge(1, n, decay);
				}
				if((n.getID()%5) < 4 && n.getID() < 20){
					n.addEdge(1, nodes.get(n.getID()+6), decay);
					nodes.get(n.getID()+6).addEdge(1, n, decay);
				}
			}
		}
	}
	
	/**
	 * constructor to create a random graph looking like a grid
	 * and to place random units of gold on the nodes
	 * @param rnd Random variable to place gold
	 * @param nodecount can be 2, 4, 9, 25 or 100 (25 by default)
	 */
	public Graph(Random rnd, int nodecount) {
		this(rnd, nodecount, 1, 0.9);
	}
	
	/**
	 * constructor to create an empty graph
	 */
	public Graph(){
		nodes = new ArrayList<Node>();
	}
	
	/**
	 * adds a note to the graph and returns its id
	 * @return id of the created node
	 * @throws exception if the graph is at full capacity
	 */
	public int addNode(int x, int y, int gold) throws ArrayIndexOutOfBoundsException{
		nodes.add(new Node(nodeID, x, y, gold));
		nodeID++;
		return (nodeID-1);
	}
	
	/**
	 * adds/edits/removes an edge between two nodes
	 * @param nodeID1 id of first node
	 * @param nodeID2 id of second node
	 * @param weight (new) weight of the edge, 0 if edge is to be deleted
	 * @throws exception if at least one of the nodes requested does not exist
	 */
	public void editEdge (int nodeID1, int nodeID2, int weight) throws IllegalArgumentException{
		if(nodeID1 == nodeID2 || nodeID1 > nodeID || nodeID2 > nodeID || nodeID1 < 0 || nodeID2 < 0)
			throw new IllegalArgumentException("Cannot add edge - at least one of the nodes does not exist or nodes are the same!");
		try{
			nodes.get(nodeID1).removeEdge(nodes.get(nodeID2));
			nodes.get(nodeID2).removeEdge(nodes.get(nodeID1));
		} catch(IllegalArgumentException e){
			// just trying to delete, ignore exception if no edge exists ... dirty but effective hack
		}
		finally{
			if(weight != 0){
				nodes.get(nodeID1).addEdge(weight, nodes.get(nodeID2));
				nodes.get(nodeID2).addEdge(weight, nodes.get(nodeID1));
			}
		}
	}
	
	@Override
	public String toString(){
		String str = "Number of nodes: " + nodeID + "\n";
		for (Node n : nodes){
			str += "Node " + n.id + " - coords: (" + n.getX() + "," + n.getY() + ") - gold: " + n.getGold() + " - connects to: ";
			for (Edge e : n.getEdges())
				str += "" + e.getTarget().getID() + " ";
			str += "\n";
		}
		return str;
	}
	
	/**
	 * returns all nodes as an ArrayList for drawing purposes 
	 * @return ArrayList<Node> for GUI
	 */
	public ArrayList<Node> getNodes(){
		return nodes;
	}
	
	/**
	 * removes node with given ID and adjusts ID of all following nodes
	 * @param nodeID
	 */
	public void removeLastNode(){
		for(Edge e : nodes.get(nodeID-1).getEdges())
			e.getTarget().removeEdge(nodes.get(nodeID-1));
		nodes.remove(nodes.size()-1);
		nodeID--;
	}
	
	public boolean equals(Object o){
		// something doesn't add up => not equal
		try{
			Graph other = (Graph)o;
			// same amount of nodes?
			if(other.getNodes().size()!=this.nodes.size())
				return false;
			for(int i=0;i<nodes.size();i++){
				// every node same amount of edges?
				if(nodes.get(i).getEdges().size() != ((Node)other.getNodes().get(i)).getEdges().size())
					return false;
				for(int j=0; j<nodes.get(i).getEdges().size();j++){
					// every edge equal?
					if(!nodes.get(i).getEdges().get(j).equals(((Node)other.getNodes().get(i)).getEdges().get(j)))
						return false;
				}
			}
			return true;
		}catch(Exception e){
			return false;
		}
	}
	
}
